package com.xbongbong.saas.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.i18n.util.I18nMessageUtil;
import com.xbongbong.paas.constant.MessageConstant;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.domain.entity.PaasFormEntity;
import com.xbongbong.paas.domain.entity.PaasFormExplainEntity;
import com.xbongbong.paas.domain.entity.ext.PaasFormEntityExt;
import com.xbongbong.paas.elasticsearch.model.PaasEsModel;
import com.xbongbong.paas.elasticsearch.pojo.XbbAggregatedPage;
import com.xbongbong.paas.elasticsearch.pojo.dto.DeleteDTO;
import com.xbongbong.paas.elasticsearch.util.EsHelper;
import com.xbongbong.paas.elasticsearch.util.XbbElasticsearchRestTemplate;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.enums.FieldTypeEnum;
import com.xbongbong.paas.enums.FlowStatusEnum;
import com.xbongbong.paas.enums.IndexTypeEnum;
import com.xbongbong.paas.field.FieldAttrEntity;
import com.xbongbong.paas.help.FormDataListSearchHelp;
import com.xbongbong.paas.help.SerialNoHelp;
import com.xbongbong.paas.model.PaasFormExplainModel;
import com.xbongbong.paas.model.PaasFormModel;
import com.xbongbong.paas.pojo.dto.SerialNoDTO;
import com.xbongbong.paas.pojo.vo.SerialNoVO;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.helper.PageHelper;
import com.xbongbong.paas.toolbox.util.Arith;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.FormulaUtil;
import com.xbongbong.paas.toolbox.util.PageHelperUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.toolbox.wrap.BaseVO;
import com.xbongbong.paas.util.ExplainUtil;
import com.xbongbong.pro.constant.BusinessConstant;
import com.xbongbong.pro.constant.ElasticConstant;
import com.xbongbong.pro.constant.I18nStringConstant;
import com.xbongbong.pro.customerstatement.pojo.StatementCustomerPojo;
import com.xbongbong.pro.customerstatement.pojo.StatementTableDataExportPojo;
import com.xbongbong.pro.customerstatement.pojo.StatementTableDataPojo;
import com.xbongbong.pro.customerstatement.pojo.dto.StatementCondition;
import com.xbongbong.pro.customerstatement.pojo.dto.StatementCustomerAddDTO;
import com.xbongbong.pro.customerstatement.pojo.dto.StatementSummaryDetailDTO;
import com.xbongbong.pro.customerstatement.pojo.dto.StatementSummaryOperateDTO;
import com.xbongbong.pro.customerstatement.pojo.dto.batch.StatementBatchAddDTO;
import com.xbongbong.pro.customerstatement.pojo.dto.batch.StatementBatchDeleteDTO;
import com.xbongbong.pro.customerstatement.pojo.vo.StatementCustomerAddVO;
import com.xbongbong.pro.customerstatement.pojo.vo.StatementSummaryDetailVO;
import com.xbongbong.pro.domain.entity.UpdateDataEntity;
import com.xbongbong.pro.enums.DistributorMarkEnum;
import com.xbongbong.pro.enums.ErrorCodeEnum;
import com.xbongbong.pro.enums.SaasMarkEnum;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.message.constant.StatementPushConstant;
import com.xbongbong.pro.message.enums.PushTypeEnum;
import com.xbongbong.pro.message.help.PushHelper;
import com.xbongbong.pro.message.pojo.PushRefTypePojo;
import com.xbongbong.pro.message.pojo.dto.DistributorMessageRabbitMqDTO;
import com.xbongbong.pro.message.pojo.dto.MessageRabbitMqDTO;
import com.xbongbong.pro.rabbitmq.producer.BaseProducer;
import com.xbongbong.pro.service.toolbox.statistic.help.ChartQueryHelp;
import com.xbongbong.pro.statistic.through.pojo.TableDataInfoPojo;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.ErrorMessageConstant;
import com.xbongbong.saas.constant.PageConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.constant.fund.FundSetConstant;
import com.xbongbong.saas.domain.entity.CustomerStatementEntity;
import com.xbongbong.saas.domain.entity.CustomerStatementProductEntity;
import com.xbongbong.saas.domain.entity.CustomerStatementSnapshotEntity;
import com.xbongbong.saas.domain.entity.CustomerStatementStatisticsEntity;
import com.xbongbong.saas.domain.entity.DistributorAccountEntity;
import com.xbongbong.saas.domain.entity.OutstockProductEntity;
import com.xbongbong.saas.domain.entity.ext.CustomerEntityExt;
import com.xbongbong.saas.domain.entity.ext.OutstockEntityExt;
import com.xbongbong.saas.enums.CustomerStatementEnum;
import com.xbongbong.saas.enums.CustomerStatementSnapshotEnum;
import com.xbongbong.saas.enums.CustomerStatementStatisticDistributorEnum;
import com.xbongbong.saas.enums.CustomerStatementStatisticEnum;
import com.xbongbong.saas.enums.CustomerStatementStatusEnum;
import com.xbongbong.saas.enums.ListOptionTabEnum;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.base.ManagementBaseEnum;
import com.xbongbong.saas.enums.dictionary.ArchivedEnum;
import com.xbongbong.saas.enums.dictionary.DistributorRoleEnum;
import com.xbongbong.saas.help.FundHelp;
import com.xbongbong.saas.model.CustomerModel;
import com.xbongbong.saas.model.CustomerStatementProductModel;
import com.xbongbong.saas.model.CustomerStatementSnapshotModel;
import com.xbongbong.saas.model.CustomerStatementStatisticsModel;
import com.xbongbong.saas.model.DistributorAccountModel;
import com.xbongbong.saas.model.OutstockModel;
import com.xbongbong.saas.model.OutstockProductModel;
import com.xbongbong.saas.service.CustomerStatementService;
import com.xbongbong.saas.service.CustomerStatementStatisticService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.xbongbong.paas.elasticsearch.constant.EsNormalConstant.MINI_MUM_SHOULD_MATCH;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;

/**
 * @Description 对账单列表
 * @author: shian
 * @date : 2021/1/8
 */
@Service("customerStatementStatisticService")
public class CustomerStatementStatisticServiceImpl implements CustomerStatementStatisticService {

    private static final Logger LOG = LoggerFactory.getLogger(CustomerStatementStatisticServiceImpl.class);

    private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
            60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), new ThreadPoolExecutor.CallerRunsPolicy());

    @Resource
    private CustomerModel customerModel;
    @Resource
    private PaasFormModel paasFormModel;
    @Resource
    private FundHelp fundHelp;
    @Resource
    private EsHelper esHelper;
    @Resource
    private ChartQueryHelp chartQueryHelp;
    @Resource
    private PaasFormExplainModel paasFormExplainModel;
    @Resource
    private CustomerStatementStatisticsModel customerStatementStatisticsModel;
    @Resource
    private SerialNoHelp serialNoHelp;
    @Resource
    private CustomerStatementSnapshotModel customerStatementSnapshotModel;
    @Resource
    private PaasEsModel paasEsModel;
    @Resource
    private FormDataListSearchHelp formDataListSearchHelp;
    @Resource
    private XbbElasticsearchRestTemplate xbbElasticsearchRestTemplate;
    @Resource
    private CustomerStatementService customerStatementService;
    @Resource
    private OutstockModel outstockModel;
    @Resource
    private OutstockProductModel outstockProductModel;
    @Resource
    private CustomerStatementProductModel customerStatementProductModel;
    @Resource
    private PushHelper pushHelper;
    @Resource
    private BaseProducer baseProducer;
    @Resource
    private DistributorAccountModel distributorAccountModel;

    @Transactional
    @Override
    public StatementCustomerAddVO generate(StatementCustomerAddDTO statementCustomerAddDTO) throws XbbException {
        StatementCustomerAddVO addVO = new StatementCustomerAddVO();
        String corpid = statementCustomerAddDTO.getCorpid();
        Long customerId = statementCustomerAddDTO.getCustomerId();
        Integer distributorMark = statementCustomerAddDTO.getDistributorMark();
        CustomerEntityExt customerEntity = customerModel.getByKey(customerId, corpid);
        PaasFormEntityExt paasFormEntityExt = paasFormModel.getByBusinessType4Distributor(XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode(), corpid, distributorMark, null);
        List<Long> checkingDate = statementCustomerAddDTO.getCheckingDate();
        StatementCustomerPojo statementCustomerPojo = fundHelp.getStatementCustomerPojo(checkingDate, customerId, null);
        // 校验对帐日期是否有交叉,不允许对账周期有交集,重新生成时不校验
        if (statementCustomerAddDTO.getRegenerateId() == null) {
            if (checkRepeatStatement(customerId, corpid, statementCustomerPojo) > 0) {
                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100018.getCode(), ErrorMessageConstant.CHECK_DATE_REPEAT_ERROR);
            }
        }

        // 1.获取最新的对账数据
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.statementQuery4Table(corpid, statementCustomerPojo);
        // 限制返回值
        List<String> fieldList = new ArrayList<>();
        fieldList.add(BusinessConstant.ID);
        for (CustomerStatementEnum customerStatementEnum : CustomerStatementEnum.values()) {
            fieldList.add("data." + customerStatementEnum.getAttr());
        }
        //时间已经校验过
        Long firstTime = checkingDate.get(0);
        List<CustomerStatementEntity> list = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT, boolQueryBuilder, CustomerStatementEntity.class, fieldList);

        // 2.计算总计
        double beginAmount= fundHelp.getStatementBegining(corpid, statementCustomerPojo);
        String amountReceivableAttr = CustomerStatementEnum.getEsAttr(CustomerStatementEnum.AMOUNT_RECEIVABLE);
        String amountReceivedAttr = CustomerStatementEnum.getEsAttr(CustomerStatementEnum.AMOUNT_RECEIVED);
        String writeOffAmountAttr = CustomerStatementEnum.getEsAttr(CustomerStatementEnum.WRITE_OFF_AMOUNT);
        List<String> sumFieldList = Arrays.asList(amountReceivableAttr, amountReceivedAttr, writeOffAmountAttr);
        //一次性汇总应收金额、回款金额、核销金额
        JSONObject result = esHelper.sumMuchField4Some(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT, boolQueryBuilder, sumFieldList);
        double totalAmountReceivable = result.getDoubleValue(amountReceivableAttr);
        double totalAmountReceived = result.getDoubleValue(amountReceivedAttr);
        double totalWriteOffAmount = result.getDoubleValue(writeOffAmountAttr);
        //总应收余额 = 期初 + 总应收金额 - 总核销金额
        double totalBalanceReceivable = Arith.sub(Arith.add(beginAmount, totalAmountReceivable), totalWriteOffAmount);

        // 3.生成一条对账信息
        Long formId = paasFormEntityExt.getId();
        PaasFormExplainEntity paasFormExplainEntity = paasFormExplainModel.getByFormId(formId, corpid);
        CustomerStatementStatisticsEntity statisticsEntity;
        JSONObject statisticsData = new JSONObject();
        if (statementCustomerAddDTO.getRegenerateId() == null) {
            statisticsEntity = new CustomerStatementStatisticsEntity();
            statisticsEntity.setSerialNo("");
            Optional<FieldAttrEntity> optional = JSONArray.parseArray(paasFormExplainEntity.getExplains(), FieldAttrEntity.class).stream()
                    .filter(item -> Objects.equals(item.getFieldType(), FieldTypeEnum.SERIALNO.getType())).findFirst();
            if (optional.isPresent()) {
                // 编号生成
                FieldAttrEntity fieldAttrEntity = optional.get();
                SerialNoDTO serialNoDTO = new SerialNoDTO();
                serialNoDTO.setCorpid(corpid);
                serialNoDTO.setFormId(formId);
                serialNoDTO.setSerialNumber(fieldAttrEntity.getSerialNumber());
                // 生成流水号
                SerialNoVO serialNoVO = serialNoHelp.getSerialNo(serialNoDTO);
                if (serialNoVO == null || StringUtils.isEmpty(serialNoVO.getSerialNo())) {
                    throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, MessageConstant.NO_SERIAL_NO);
                }
                statisticsEntity.setSerialNo(serialNoVO.getSerialNo());
            }
        } else {
            statisticsEntity = customerStatementStatisticsModel.getByKey(statementCustomerAddDTO.getRegenerateId(), corpid);
            statisticsData.put(CustomerStatementStatisticDistributorEnum.CONFIRM_DATE.getAttr(), null);
            statisticsData.put(CustomerStatementStatisticDistributorEnum.DEALER_CONFIRMOR.getAttr(), null);
            statisticsData.put(CustomerStatementStatisticEnum.CONFIRMOR_LINK_TEXT.getAttr(), null);
        }
        statisticsData.put(CustomerStatementStatisticEnum.CUSTOMER_ID.getAttr(), customerId);
        statisticsData.put(CustomerStatementStatisticDistributorEnum.STATUS.getAttr(), CustomerStatementStatusEnum.UNCONFIRMED.getCode());
        statisticsData.put(CustomerStatementStatisticEnum.CREATE_DATE.getAttr(), DateTimeUtil.getInt());
        statisticsData.put(CustomerStatementStatisticEnum.AMOUNT_RECEIVABLE_TOTAL.getAttr(), totalAmountReceivable);
        statisticsData.put(CustomerStatementStatisticEnum.AMOUNT_RECEIVED_TOTAL.getAttr(), totalAmountReceived);
        statisticsData.put(CustomerStatementStatisticEnum.WRITE_OFF_AMOUNT_TOTAL.getAttr(), totalWriteOffAmount);
        statisticsData.put(CustomerStatementStatisticEnum.BALANCE_RECEIVABLE_TOTAL.getAttr(), totalBalanceReceivable);
        statisticsData.put(CustomerStatementStatisticEnum.IS_ARCHIVED.getAttr(), ArchivedEnum.UN_ARCHIVED.getCode());
        statisticsData.put(CustomerStatementStatisticEnum.LINK_CUSTOMER.getAttr(), customerEntity.getData().get(ManagementBaseEnum.NAME.getAttr()));
        statisticsData.put(CustomerStatementStatisticEnum.CREATOR.getAttr(), statementCustomerAddDTO.getUserId());
        //对账日期
        JSONObject checkDate = new JSONObject();
        checkDate.put("startTime",firstTime);
        checkDate.put("endTime",checkingDate.get(1));
        statisticsData.put(CustomerStatementStatisticEnum.CHECK_DATE.getAttr(), checkDate);
        statisticsEntity.setData(statisticsData);
        statisticsEntity.setCreatorId(statementCustomerAddDTO.getUserId());
        statisticsEntity.setOwnerId(customerEntity.getOwnerId());
        statisticsEntity.setCorpid(corpid);
        statisticsEntity.setAppId(statementCustomerAddDTO.getAppId());
        statisticsEntity.setMenuId(paasFormEntityExt.getMenuId());
        statisticsEntity.setFormId(paasFormEntityExt.getId());
        statisticsEntity.setFlowStatus(FlowStatusEnum.NOT_NEED_APPROVAL.getType());

        Long id = customerStatementStatisticsModel.save(statisticsEntity);

        try {
            // 4.数据存入到对账流水快照中
            addSnapshotData(corpid, list, firstTime, beginAmount, id, distributorMark);
        } catch (Exception e) {
            throw new XbbException(ErrorCodeEnum.API_FAIL,ErrorMessageConstant.STATEMENT_GENERATE_FAIL);
        }
        // 对账单生成-消息推送
        try {
            statementGeneratePush(statementCustomerAddDTO, statisticsEntity);
        } catch (XbbException e) {
            LOG.error("对账单生成消息推送：", e);
        }
        addVO.setSummaryId(id);
        return addVO;
    }

    /**
     * 对账单生成消息
     * @param statementCustomerAddDTO
     * @param statisticsEntity
     */
    private void statementGeneratePush(StatementCustomerAddDTO statementCustomerAddDTO, CustomerStatementStatisticsEntity statisticsEntity) throws XbbException {
        Long dataId = statisticsEntity.getId();
        String corpid = statisticsEntity.getCorpid();
        List<String> pushUserList = pushHelper.getUserIdListByPushSetRule(corpid, PushTypeEnum.STATEMENT_GENERATE_PUSH.getCode(),
                PushTypeEnum.STATEMENT_GENERATE_PUSH.getSubCode(), statementCustomerAddDTO.getLoginUser(), statisticsEntity);
        PushRefTypePojo.Options options = new PushRefTypePojo.Options(statisticsEntity.getAppId(), statisticsEntity.getMenuId(),
                statisticsEntity.getFormId(), dataId, Collections.singletonList(dataId), SaasMarkEnum.SAAS.getCode(),
                XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode(), XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode());
        MessageRabbitMqDTO messageRabbitMqDTO = new MessageRabbitMqDTO(corpid, pushUserList, Collections.singletonList(dataId), 0, null,
                I18nMessageUtil.getMessage(StatementPushConstant.STATEMENT_GENERATE_TITLE), String.format(I18nMessageUtil.getMessage(StatementPushConstant.STATEMENT_GENERATE_CONTENT), statisticsEntity.getSerialNo()) , null, options);
        baseProducer.sendMessage(PushTypeEnum.STATEMENT_GENERATE_PUSH, messageRabbitMqDTO);
        if (Objects.equals(statementCustomerAddDTO.getDistributorMark(), DistributorMarkEnum.DISTRIBUTOR.getCode())) {
            // 使用经销商模板时需要发送消息给经销商管理员
            long distributorId = statisticsEntity.getData().getLongValue(CustomerStatementStatisticDistributorEnum.CUSTOMER_ID.getAttr());
            List<DistributorAccountEntity> distributorAccountEntityList = distributorAccountModel.getAllDistributorUserList(corpid, distributorId, DistributorRoleEnum.MANAGER.getCode());
            List<String> pushDistributorUserList = distributorAccountEntityList.stream().map(item -> String.valueOf(item.getId())).collect(Collectors.toList());
            DistributorMessageRabbitMqDTO distributorMessageRabbitMqDTO;
            if (Objects.isNull(statementCustomerAddDTO.getRegenerateId())) {
                distributorMessageRabbitMqDTO = new DistributorMessageRabbitMqDTO(corpid, pushDistributorUserList, Collections.singletonList(dataId), 0, null,
                        I18nMessageUtil.getMessage(StatementPushConstant.DISTRIBUTOR_STATEMENT_GENERATE_TITLE), String.format(I18nMessageUtil.getMessage(StatementPushConstant.DISTRIBUTOR_STATEMENT_GENERATE_CONTENT), statisticsEntity.getSerialNo()), null, options, DistributorMarkEnum.DISTRIBUTOR.getCode());
                baseProducer.sendMessage(PushTypeEnum.STATEMENT_GENERATE_PUSH, distributorMessageRabbitMqDTO);
            } else {
                distributorMessageRabbitMqDTO = new DistributorMessageRabbitMqDTO(corpid, pushDistributorUserList, Collections.singletonList(dataId), 0, null,
                        I18nMessageUtil.getMessage(StatementPushConstant.STATEMENT_REGENERATE_TITLE), String.format(I18nMessageUtil.getMessage(StatementPushConstant.STATEMENT_REGENERATE_CONTENT), statisticsEntity.getSerialNo()), null, options, DistributorMarkEnum.DISTRIBUTOR.getCode());
                baseProducer.sendMessage(PushTypeEnum.STATEMENT_REGENERATE_PUSH, distributorMessageRabbitMqDTO);
            }
        }
    }

    /**
     * 保存快照数据
     * @param corpid
     * @param list 对账单流水数据
     * @param firstTime 对账开始时间
     * @param beginAmount
     * @param id 对账单id
     * @throws XbbException
     */
    private void addSnapshotData(String corpid, List<CustomerStatementEntity> list, Long firstTime, double beginAmount, Long id, int distributorMark) throws XbbException{
        List<CustomerStatementSnapshotEntity> snapshotEntityList = new LinkedList<>();
        List<CustomerStatementProductEntity> productEntityList = new LinkedList<>();
        CustomerStatementSnapshotEntity snapshotBeginEntity;
        snapshotBeginEntity = new CustomerStatementSnapshotEntity();
        JSONObject data = new JSONObject();
        data.put(CustomerStatementSnapshotEnum.CHECKING_DATE.getAttr(), firstTime);
        // 期初默认business_type=0
        data.put(CustomerStatementSnapshotEnum.BUSINESS_TYPE.getAttr(), 0);
        data.put(CustomerStatementSnapshotEnum.BALANCE_RECEIVABLE.getAttr(), beginAmount);
        data.put(CustomerStatementSnapshotEnum.SUMMARY_ID.getAttr(), id);
        snapshotBeginEntity.setData(data);
        snapshotBeginEntity.setCorpid(corpid);
        long now = DateTimeUtil.getInt();
        snapshotBeginEntity.setAddTime(now);
        snapshotBeginEntity.setUpdateTime(now);
        snapshotBeginEntity.setDel(DelEnum.NORMAL.getDel());
        snapshotEntityList.add(snapshotBeginEntity);

        CustomerStatementSnapshotEntity snapshotEntity;
        for (CustomerStatementEntity entity : list) {
            snapshotEntity = new CustomerStatementSnapshotEntity();
            snapshotEntity.setData(entity.getData());
            snapshotEntity.setCorpid(corpid);
            snapshotEntity.setAddTime(now);
            snapshotEntity.setUpdateTime(now);
            snapshotEntity.setDel(DelEnum.NORMAL.getDel());
            snapshotEntity.getData().put(CustomerStatementSnapshotEnum.SUMMARY_ID.getAttr(), id);
            snapshotEntityList.add(snapshotEntity);
        }
        // 为了加个关联产品，app_id,menu_id,form_id都要保存
        PaasFormEntityExt paasFormEntityExt = paasFormModel.getByBusinessType4Distributor(XbbRefTypeEnum.CUSTOMER_STATEMENT.getCode(), corpid, distributorMark, null);
        for (CustomerStatementSnapshotEntity entity : snapshotEntityList) {
            entity.setAppId(paasFormEntityExt.getAppId());
            entity.setMenuId(paasFormEntityExt.getMenuId());
            entity.setFormId(paasFormEntityExt.getId());
        }
        customerStatementSnapshotModel.insertBatch(snapshotEntityList);
        for (CustomerStatementSnapshotEntity statementSnapshotEntity : snapshotEntityList) {
            JSONObject statementData = statementSnapshotEntity.getData();
            Long sourceId = statementData.getLong(CustomerStatementEnum.SOURCE_ID.getAttr());
            if (CollectionsUtil.isNotEmpty(statementData.getJSONArray(CustomerStatementEnum.PRODUCT.getAttr()))) {
                // 存在产品时，产品数据单独存在tb_saas_customer_statement_product中
                OutstockEntityExt outstockEntityExt = outstockModel.getByKey(sourceId, corpid);
                if (Objects.isNull(outstockEntityExt)) {
                    continue;
                }
                List<OutstockProductEntity> outstockProducts = outstockProductModel.getProductsByOutstockIdIn(corpid, Collections.singletonList(outstockEntityExt.getId()));
                for (OutstockProductEntity outstockProduct : outstockProducts) {
                    CustomerStatementProductEntity productEntity = new CustomerStatementProductEntity(corpid, 1);
                    productEntity.setStatementId(statementSnapshotEntity.getId());
                    BeanUtil.copyProperties(outstockProduct, productEntity);
                    productEntity.setId(null);
                    productEntityList.add(productEntity);
                }
            }
        }
        if (CollectionsUtil.isNotEmpty(productEntityList)) {
            customerStatementProductModel.insertBatch(productEntityList);
        }
    }

    @Override
    public StatementSummaryDetailVO detail(StatementSummaryDetailDTO detailDTO) throws XbbException {
        StatementSummaryDetailVO detailVO = new StatementSummaryDetailVO();
        String corpid = detailDTO.getCorpid();
        Integer distributorMark = detailDTO.getDistributorMark();
        List<Long> checkingDate = detailDTO.getCheckDate();
        Long dataId = detailDTO.getDataId();
        //获取应收款-回款类型的 下拉框items
        Map<Object, String> itemsMap = fundHelp.getItemsFromPaymentExplain(corpid, distributorMark);
        CustomerStatementStatisticsEntity customerStatementStatisticsEntity = customerStatementStatisticsModel.getByKey(dataId, corpid);
        JSONObject statementDate = customerStatementStatisticsEntity.getData();
        // 置入合计值
        double amountReceivable = statementDate.getDouble(CustomerStatementStatisticEnum.AMOUNT_RECEIVABLE_TOTAL.getAttr());
        double amountReceived = statementDate.getDouble(CustomerStatementStatisticEnum.AMOUNT_RECEIVED_TOTAL.getAttr());
        double writeOffAmount = statementDate.getDouble(CustomerStatementStatisticEnum.WRITE_OFF_AMOUNT_TOTAL.getAttr());
        double balanceReceivable = statementDate.getDouble(CustomerStatementStatisticEnum.BALANCE_RECEIVABLE_TOTAL.getAttr());
        String totalAmountReceivableStr = fundHelp.formatMoneyStr(amountReceivable);
        String totalAmountReceivedStr = fundHelp.formatMoneyStr(amountReceived);
        String totalWriteOffAmountStr = fundHelp.formatMoneyStr(writeOffAmount);
        String totalBalanceReceivableStr = fundHelp.formatMoneyStr(balanceReceivable);
        StatementTableDataPojo totalPojo = new StatementTableDataPojo();
        totalPojo.setAmountReceivable(new TableDataInfoPojo(totalAmountReceivableStr));
        totalPojo.setAmountReceived(new TableDataInfoPojo(totalAmountReceivedStr));
        totalPojo.setWriteOffAmount(new TableDataInfoPojo(totalWriteOffAmountStr));
        totalPojo.setBalanceReceivable(new TableDataInfoPojo(totalBalanceReceivableStr));
        detailVO.setTotalPojo(totalPojo);
        if (checkingDate.size() == 0) {
            // 无对账日期筛选时，需获取该对账单的对账日期
            JSONObject checkObj = statementDate.getJSONObject(CustomerStatementStatisticEnum.CHECK_DATE.getAttr());
            checkingDate.add(checkObj.getLongValue("startTime"));
            checkingDate.add(checkObj.getLongValue("endTime"));
        }
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery("corpid.keyword", corpid));
        boolQueryBuilder.filter(termQuery(CustomerStatementSnapshotEnum.getEsAttr(CustomerStatementSnapshotEnum.SUMMARY_ID), dataId));
        boolQueryBuilder.filter(termQuery("del", 0));
        boolQueryBuilder.filter(rangeQuery("data." + CustomerStatementSnapshotEnum.CHECKING_DATE.getAttr()).gte(checkingDate.get(0)).lte(checkingDate.get(1)));
        //排序：checkingDate asc，id asc
        List<SortBuilder> sortBuilderList = Arrays.asList(new FieldSortBuilder(CustomerStatementSnapshotEnum.getEsAttr(CustomerStatementSnapshotEnum.CHECKING_DATE)).order(SortOrder.ASC), new FieldSortBuilder(BasicConstant.ID).order(SortOrder.ASC));
        // 限制返回值
        List<String> fieldList = new ArrayList<>();
        fieldList.add(BusinessConstant.ID);
        for (CustomerStatementSnapshotEnum snapshotEnum : CustomerStatementSnapshotEnum.values()) {
            fieldList.add("data." + snapshotEnum.getAttr());
        }
        fieldList.add(StringConstant.CORPID);
        Integer page = detailDTO.getPage();
        Integer pageSize = detailDTO.getPageSize();
        boolean printFlag = Objects.equals(detailDTO.getFromPrint(), BasicConstant.ONE);
        String beginTime = DateTimeUtil.getStringEpochSecond(checkingDate.get(0), DateTimeUtil.SDFDate);
        String beginName = I18nMessageUtil.getMessage(FundSetConstant.BEGINNING);
        double beginAmount = 0D;
        // 对账流水是否有产品信息
        boolean containProduct = false;
        if (printFlag) {
            List<StatementTableDataExportPojo> printData = new ArrayList<>();
            // 获取该对账单的所有快照数据
            List<CustomerStatementSnapshotEntity> list = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT_SNAPSHOT, boolQueryBuilder, CustomerStatementSnapshotEntity.class, fieldList, sortBuilderList);
            //按照对账日期升序排
            list = list.stream().sorted(Comparator.comparingLong(item -> item.getData().getLongValue(CustomerStatementEnum.CHECKING_DATE.getAttr()))).collect(Collectors.toList());
            if (CollectionsUtil.isNotEmpty(list)) {
                CustomerStatementSnapshotEntity snapshotEntity = list.get(0);
                if (Objects.equals(snapshotEntity.getData().getIntValue(CustomerStatementSnapshotEnum.BUSINESS_TYPE.getAttr()), 0)) {
                    // 表示第一条数据为期初
                    beginAmount = snapshotEntity.getData().getDoubleValue(CustomerStatementSnapshotEnum.BALANCE_RECEIVABLE.getAttr());
                    String beginAmountStr = StringUtil.formatDouble(beginAmount);
                    printData.add(new StatementTableDataExportPojo(beginTime, I18nMessageUtil.getMessage(FundSetConstant.BEGINNING), beginAmountStr));
                    list.remove(0);
                }
            }
            //生成对账单添加回款类型
            Map<Long, String> paymentIdMap = fundHelp.getPaymentIdMapForStatementStatisticData(list, corpid,itemsMap);
            for (CustomerStatementSnapshotEntity entity : list) {
                //因为对账单快照没有把 businessType和stetementId 列为宽表字段，所以这边需要和客户对账单区分一下
                Integer businessType = entity.getData().getIntValue(CustomerStatementSnapshotEnum.BUSINESS_TYPE.getAttr());
                Long statementId = entity.getData().getLongValue(CustomerStatementSnapshotEnum.STATEMENT_ID.getAttr());
                beginAmount = customerStatementService.formatTableData4Export(beginAmount, entity, printData, 1,paymentIdMap,businessType,statementId);
                JSONObject data = entity.getData();
                amountReceived = Arith.add(amountReceived, data.getDoubleValue(CustomerStatementEnum.AMOUNT_RECEIVED.getAttr()));
                writeOffAmount = Arith.add(writeOffAmount, data.getDoubleValue(CustomerStatementEnum.WRITE_OFF_AMOUNT.getAttr()));
            }

            //塞入合计/剩余
            //总应收余额转换为大写的中文金额
            String totalBalanceReceivableChinese = FormulaUtil.upperMoneyToChinese(totalBalanceReceivableStr);
            printData.add(new StatementTableDataExportPojo(I18nMessageUtil.getMessage(I18nStringConstant.STATEMENT_TOTAL_REMAINING), totalAmountReceivableStr, totalAmountReceivedStr, totalWriteOffAmountStr, totalBalanceReceivableStr, totalBalanceReceivableChinese));
            detailVO.setPrintData(printData);
        } else {
            XbbAggregatedPage<CustomerStatementSnapshotEntity> aggregatedPage = esHelper.findByPage(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT_SNAPSHOT, boolQueryBuilder, CustomerStatementSnapshotEntity.class, fieldList, page, pageSize, sortBuilderList);
            int rowCounts = 0;
            List<CustomerStatementSnapshotEntity> list = new ArrayList<>();
            if (aggregatedPage != null) {
                rowCounts = (int) aggregatedPage.getTotalElements();
                list = aggregatedPage.getContent();
            }

            PageHelper pageHelper = PageHelperUtil.initPageHelper(rowCounts, page, pageSize, PageConstant.DEFAULT_PAGE_SIZE);
            List<StatementTableDataPojo> data = new ArrayList<>();
            if (CollectionsUtil.isNotEmpty(list)) {
                CustomerStatementSnapshotEntity snapshotEntity = list.get(0);
                if (Objects.equals(snapshotEntity.getData().getIntValue(CustomerStatementSnapshotEnum.BUSINESS_TYPE.getAttr()), 0)) {
                    // 表示第一条数据为期初
                    beginAmount = snapshotEntity.getData().getDoubleValue(CustomerStatementSnapshotEnum.BALANCE_RECEIVABLE.getAttr());
                    String beginAmountStr = StringUtil.formatDouble(beginAmount);
                    StatementTableDataPojo beginStatementTableDataPojo = new StatementTableDataPojo(
                            new TableDataInfoPojo(beginTime),
                            new TableDataInfoPojo(beginName),
                            new TableDataInfoPojo(beginAmountStr));
                    data.add(beginStatementTableDataPojo);
                    list.remove(0);
                } else {
                    beginAmount = getFirstBalanceReceivable(corpid, snapshotEntity, customerStatementStatisticsEntity.getId());
                }
            }
            //对账单列表详情添加回款类型
            Map<Long, String> paymentIdMap = fundHelp.getPaymentIdMapForStatementStatisticData(list, corpid ,itemsMap);
            //格式化，并封装非期初信息（应收款、回款单）到data内
            for (CustomerStatementSnapshotEntity entity : list) {
                if (CollectionsUtil.isNotEmpty(entity.getData().getJSONArray(CustomerStatementSnapshotEnum.PRODUCT.getAttr()))) {
                    containProduct = true;
                }
                //因为对账单快照没有把 businessType和stetementId 列为宽表字段，所以这边需要和客户对账单区分一下
                Integer businessType = entity.getData().getIntValue(CustomerStatementSnapshotEnum.BUSINESS_TYPE.getAttr());
                Long statementId = entity.getData().getLongValue(CustomerStatementSnapshotEnum.STATEMENT_ID.getAttr());
                beginAmount = customerStatementService.formatTableData(corpid, beginAmount, entity, data, 1,paymentIdMap,businessType,statementId);
            }
            //对账表-表头
            detailVO.setTitle(CustomerStatementServiceImpl.packageTitle(containProduct, distributorMark));
            detailVO.setData(data);
            detailVO.setPageHelper(pageHelper);
            detailVO.setBusinessType(XbbRefTypeEnum.CUSTOMER_STATEMENT_SNAPSHOT.getCode());
            PaasFormEntityExt formEntityExt = paasFormModel.getByBusinessType4Distributor(XbbRefTypeEnum.CUSTOMER_STATEMENT.getCode(), corpid, distributorMark, "id");
            detailVO.setFormId(formEntityExt.getId());
            // 处理列表页字段排序
            formDataListSearchHelp.explainFieldSort4CustomerStatement(detailDTO, detailVO.getTitle());
        }
        return detailVO;
    }

    /**
     * 得到当页第一条对账记录之前的所有记录总的balanceReceivable(应收余额)：应收总金额 - 核销总金额
     * @param corpid
     * @param snapshotEntity 当页第一条对账记录实体
     * @param id 对账单数据id
     * @return
     */
    private double  getFirstBalanceReceivable(String corpid, CustomerStatementSnapshotEntity snapshotEntity, Long id) throws XbbException{
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery("corpid.keyword", corpid));
        boolQueryBuilder.filter(termQuery(CustomerStatementSnapshotEnum.getEsAttr(CustomerStatementSnapshotEnum.SUMMARY_ID), id));
        BoolQueryBuilder oneQueryBuilder = boolQuery();
        BoolQueryBuilder twoQueryBuilder = boolQuery();

        Long checkingDate = snapshotEntity.getData().getLong(CustomerStatementSnapshotEnum.CHECKING_DATE.getAttr());
        oneQueryBuilder.filter(rangeQuery(CustomerStatementSnapshotEnum.getEsAttr(CustomerStatementSnapshotEnum.CHECKING_DATE)).lt(checkingDate));
        twoQueryBuilder.filter(termQuery(CustomerStatementSnapshotEnum.getEsAttr(CustomerStatementSnapshotEnum.CHECKING_DATE), checkingDate));
        twoQueryBuilder.filter(rangeQuery(BusinessConstant.ID).lt(snapshotEntity.getId()));
        boolQueryBuilder.should(oneQueryBuilder);
        boolQueryBuilder.should(twoQueryBuilder);
        boolQueryBuilder.minimumShouldMatch(MINI_MUM_SHOULD_MATCH);
        return fundHelp.getStatementTotalBalanceReceivable(boolQueryBuilder, IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT_SNAPSHOT);
    }

    @Override
    public BaseVO operate(StatementSummaryOperateDTO operateDTO) throws XbbException {
        BaseVO baseVO = new BaseVO();
        String operate = operateDTO.getOperate();
        String corpid = operateDTO.getCorpid();
        List<Long> dataIdList = operateDTO.getDataIdList();
        List<UpdateDataEntity> updateList = new ArrayList<>();
        long distributorId;
        Long dataId;
        List<DistributorAccountEntity> distributorAccountEntityList;
        List<String> pushDistributorUserList;
        DistributorMessageRabbitMqDTO distributorMessageRabbitMqDTO;
        PushRefTypePojo.Options options;
        ListOptionTabEnum optionTabEnum = ListOptionTabEnum.getByAttr(operate);
        List<CustomerStatementStatisticsEntity> list = customerStatementStatisticsModel.getbyIds(dataIdList, corpid);
        if (optionTabEnum != null) {
            switch (optionTabEnum) {
                case REMIND_CONFIRM_STATEMENT:
                    CustomerStatementStatisticsEntity statisticsEntity = list.get(0);
                    dataId = statisticsEntity.getId();
                    // 发送一条信息提醒经销商
                    distributorId = statisticsEntity.getData().getLongValue(CustomerStatementStatisticDistributorEnum.CUSTOMER_ID.getAttr());
                    distributorAccountEntityList = distributorAccountModel.getAllDistributorUserList(corpid, distributorId, DistributorRoleEnum.MANAGER.getCode());
                    pushDistributorUserList = distributorAccountEntityList.stream().map(item -> String.valueOf(item.getId())).collect(Collectors.toList());
                    options = new PushRefTypePojo.Options(statisticsEntity.getAppId(), statisticsEntity.getMenuId(),
                            statisticsEntity.getFormId(), dataId, Collections.singletonList(dataId), SaasMarkEnum.SAAS.getCode(),
                            XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode(), XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode());
                    distributorMessageRabbitMqDTO = new DistributorMessageRabbitMqDTO(corpid, pushDistributorUserList, Collections.singletonList(dataId), 0, null,
                            I18nMessageUtil.getMessage(StatementPushConstant.STATEMENT_REMIND_CONFIRM_TITLE), String.format(I18nMessageUtil.getMessage(StatementPushConstant.STATEMENT_REMIND_CONFIRM_CONTENT), statisticsEntity.getSerialNo()), null, options, DistributorMarkEnum.DISTRIBUTOR.getCode());
                    baseProducer.sendMessage(PushTypeEnum.STATEMENT_REMIND_CONFIRM_PUSH, distributorMessageRabbitMqDTO);
                    break;
                case VALET_CONFIRM_STATEMENT:
                    for (CustomerStatementStatisticsEntity entity : list) {
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put(CustomerStatementStatisticDistributorEnum.STATUS.getAttr(), CustomerStatementStatusEnum.CONFIRMED.getCode());
                        jsonObject.put(CustomerStatementStatisticDistributorEnum.CONFIRM_DATE.getAttr(), DateTimeUtil.getInt());
                        UpdateDataEntity updateData = ExplainUtil.getUpdateData(entity.getId(), jsonObject, corpid);
                        updateList.add(updateData);
                    }
                    try {
                        customerStatementStatisticsModel.updateBatch(updateList, corpid);
                        // 发送一条信息提醒经销商
                        for (CustomerStatementStatisticsEntity entity : list) {
                            distributorId = entity.getData().getLongValue(CustomerStatementStatisticDistributorEnum.CUSTOMER_ID.getAttr());
                            dataId = entity.getId();
                            distributorAccountEntityList = distributorAccountModel.getAllDistributorUserList(corpid, distributorId, DistributorRoleEnum.MANAGER.getCode());
                            pushDistributorUserList = distributorAccountEntityList.stream().map(item -> String.valueOf(item.getId())).collect(Collectors.toList());
                            options = new PushRefTypePojo.Options(entity.getAppId(), entity.getMenuId(),
                                    entity.getFormId(), dataId, Collections.singletonList(dataId), SaasMarkEnum.SAAS.getCode(),
                                    XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode(), XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode());
                            distributorMessageRabbitMqDTO = new DistributorMessageRabbitMqDTO(corpid, pushDistributorUserList, Collections.singletonList(dataId), 0, null,
                                    I18nMessageUtil.getMessage(StatementPushConstant.STATEMENT_VALET_CONFIRM_TITLE), String.format(I18nMessageUtil.getMessage(StatementPushConstant.STATEMENT_VALET_CONFIRM_CONTENT), entity.getSerialNo()), null, options, DistributorMarkEnum.DISTRIBUTOR.getCode());
                            baseProducer.sendMessage(PushTypeEnum.STATEMENT_VALET_CONFIRM_PUSH, distributorMessageRabbitMqDTO);
                        }
                    } catch (Exception exception) {
                        LOG.info(exception.getMessage());
                        throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001);
                    }
                    break;
                case REGENERATE_STATEMENT:
                    // 物理删除原先快照中对应的对账单数据，重新生成后，原先数据不可追溯
                    // 物理删除原因：每次重新生成都会产生重复数据，如果不物理删除，会产生大量无用的数据
                    for (CustomerStatementStatisticsEntity entity : list) {
                        BoolQueryBuilder queryBuilder = boolQuery();
                        queryBuilder.filter(termQuery("corpid.keyword", corpid));
                        queryBuilder.filter(termQuery("data." + CustomerStatementSnapshotEnum.SUMMARY_ID.getAttr(),entity.getId()));
                        List<CustomerStatementSnapshotEntity> snapshotEntityList = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT_SNAPSHOT, queryBuilder, CustomerStatementSnapshotEntity.class, Collections.singletonList(BusinessConstant.ID));
                        List<Long> snapshotIds = new ArrayList<>();
                        if (CollectionsUtil.isNotEmpty(snapshotEntityList)) {
                            snapshotIds = snapshotEntityList.stream().map(CustomerStatementSnapshotEntity::getId).collect(Collectors.toList());
                        }
                        customerStatementSnapshotModel.physicalDeleteBatch(snapshotIds, corpid);
                        List<DeleteDTO> deleteSubList = new ArrayList<>();
                        for (Long snapshotId : snapshotIds) {
                            DeleteDTO deleteDTO = new DeleteDTO();
                            deleteDTO.setEsId(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT_SNAPSHOT.getType() + "_" + corpid + "_" + snapshotId);
                            deleteDTO.setIndexTypeEnum(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT_SNAPSHOT);
                            deleteDTO.setParent(corpid + "_" + entity.getId());
                            deleteSubList.add(deleteDTO);
                        }
                        paasEsModel.physicalDeleteBatchChild(deleteSubList, corpid, IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT_SNAPSHOT);
                        StatementCustomerAddDTO statementCustomerAddDTO = new StatementCustomerAddDTO();
                        BeanUtil.copyProperties(operateDTO, statementCustomerAddDTO);
                        statementCustomerAddDTO.setCustomerId(entity.getData().getLongValue(CustomerStatementStatisticEnum.CUSTOMER_ID.getAttr()));
                        JSONObject checkDate = entity.getData().getJSONObject(CustomerStatementStatisticEnum.CHECK_DATE.getAttr());
                        List<Long> checkingDate = new ArrayList<>();
                        checkingDate.add(checkDate.getLongValue("startTime"));
                        checkingDate.add(checkDate.getLongValue("endTime"));
                        statementCustomerAddDTO.setCheckingDate(checkingDate);
                        statementCustomerAddDTO.setRegenerateId(entity.getId());
                        generate(statementCustomerAddDTO);
                    }
                    break;
                default:
                    break;
            }
        }
        return baseVO;
    }

    @Override
    @Transactional(rollbackFor = XbbException.class)
    public void deleteBatch(StatementBatchDeleteDTO statementBatchDeleteDTO) throws XbbException {
        List<Long> dataIdList = statementBatchDeleteDTO.getDataIdList();
        String corpid = statementBatchDeleteDTO.getCorpid();
        try {
            customerStatementStatisticsModel.deleteBatch(dataIdList, corpid);
            //同步删除相关的对账流水快照数据
            BoolQueryBuilder boolQueryBuilder = boolQuery();
            boolQueryBuilder.filter(termQuery("corpid.keyword", corpid));
            boolQueryBuilder.filter(termsQuery("data." + CustomerStatementSnapshotEnum.SUMMARY_ID.getAttr(), dataIdList));
            boolQueryBuilder.filter(termQuery("del", DelEnum.NORMAL.getDel()));
            List<String> fieldList = Arrays.asList(BasicConstant.ID, CustomerStatementSnapshotEnum.getEsAttr(CustomerStatementSnapshotEnum.SUMMARY_ID));
            List<CustomerStatementSnapshotEntity> result = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT_SNAPSHOT, boolQueryBuilder, CustomerStatementSnapshotEntity.class, fieldList);
            List<Long> snapshotIds = new ArrayList<>();
            if (CollectionsUtil.isNotEmpty(result)) {
                snapshotIds = result.stream().map(CustomerStatementSnapshotEntity::getId).collect(Collectors.toList());
            }
            customerStatementSnapshotModel.deleteBatch(snapshotIds, corpid, result);
        } catch (Exception exception) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001);
        }
    }

    @Override
    public void createStatement(List<StatementBatchAddDTO> batchAddList) {
        for (StatementBatchAddDTO statementBatchAddDTO : batchAddList) {
            threadPoolExecutor.execute(() -> {
                String corpid = statementBatchAddDTO.getCorpid();
                Map<String, Object> param = new HashMap<>(16);
                param.put("corpid", corpid);
                param.put("del", DelEnum.NORMAL.getDel());
                param.put("businessType", XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode());
                List<PaasFormEntity> paasFormEntityList = paasFormModel.findEntity(param);
                Map<Integer, PaasFormEntity> formEntityMap = paasFormEntityList.stream().collect(Collectors.toMap(PaasFormEntity::getDistributorMark, v->v));
                PaasFormExplainEntity paasFormExplainEntity = new PaasFormExplainEntity();
                try {
                    paasFormExplainEntity = paasFormExplainModel.getByBusinessType(XbbRefTypeEnum.CUSTOMER_STATEMENT_STATISTIC.getCode(), corpid);
                } catch (XbbException exception) {
                    LOG.error(corpid + "获取对账单表单解释出错");
                }
                List<StatementCondition> conditionList = statementBatchAddDTO.getConditionList();
                List<PaasFormEntityExt> customerForms = paasFormModel.getEnableFormList(corpid, XbbRefTypeEnum.CUSTOMER_MANAGEMENT.getCode());
                Map<Long, PaasFormEntityExt> customerFormMap = customerForms.stream().collect(Collectors.toMap(PaasFormEntityExt::getId, v->v, (k1,k2)->k1));
                List<CustomerEntityExt> customerEntityExts = customerModel.getByKeys(conditionList.stream().map(StatementCondition::getCustomerId).collect(Collectors.toList()), corpid);
                Map<Long, CustomerEntityExt> customerMap = customerEntityExts.stream().collect(Collectors.toMap(CustomerEntityExt::getId, v->v));
                for (StatementCondition condition : conditionList) {
                    try {
                        Long customerId = condition.getCustomerId();
                        List<Long> checkingDate = condition.getCheckingDate();
                        CustomerEntityExt customerEntity = customerMap.get(customerId);
                        // 获取客户是否属于经销商，匹配对应的对账单form
                        int distributorMark = customerFormMap.get(customerEntity.getFormId()).getDistributorMark();
                        PaasFormEntity paasFormEntity = formEntityMap.get(distributorMark);
                        Long formId = paasFormEntity.getId();
                        StatementCustomerPojo statementCustomerPojo = fundHelp.getStatementCustomerPojo(checkingDate, customerId, null);
                        if (checkRepeatStatement(customerId, corpid, statementCustomerPojo) > 0) {
                            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100018.getCode(), ErrorMessageConstant.CHECK_DATE_REPEAT_ERROR);
                        }
                        // 1.获取最新的对账数据
                        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.statementQuery4Table(corpid, statementCustomerPojo);
                        // 限制返回值
                        List<String> fieldList = new ArrayList<>();
                        fieldList.add(BusinessConstant.ID);
                        for (CustomerStatementEnum customerStatementEnum : CustomerStatementEnum.values()) {
                            fieldList.add("data." + customerStatementEnum.getAttr());
                        }
                        //时间已经校验过
                        Long firstTime = checkingDate.get(0);
                        List<CustomerStatementEntity> list = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT, boolQueryBuilder, CustomerStatementEntity.class, fieldList);
                        if (CollectionsUtil.isEmpty(list)) {
                            continue;
                        }
                        // 2.计算总计
                        double beginAmount= fundHelp.getStatementBegining(corpid, statementCustomerPojo);
                        String amountReceivableAttr = CustomerStatementEnum.getEsAttr(CustomerStatementEnum.AMOUNT_RECEIVABLE);
                        String amountReceivedAttr = CustomerStatementEnum.getEsAttr(CustomerStatementEnum.AMOUNT_RECEIVED);
                        String writeOffAmountAttr = CustomerStatementEnum.getEsAttr(CustomerStatementEnum.WRITE_OFF_AMOUNT);
                        List<String> sumFieldList = Arrays.asList(amountReceivableAttr, amountReceivedAttr, writeOffAmountAttr);
                        //一次性汇总应收金额、回款金额、核销金额
                        JSONObject result = esHelper.sumMuchField4Some(IndexTypeEnum.IDX_SAAS_CUSTOMER_STATEMENT, boolQueryBuilder, sumFieldList);
                        double totalAmountReceivable = result.getDoubleValue(amountReceivableAttr);
                        double totalAmountReceived = result.getDoubleValue(amountReceivedAttr);
                        double totalWriteOffAmount = result.getDoubleValue(writeOffAmountAttr);
                        //总应收余额 = 期初 + 总应收金额 - 总核销金额
                        double totalBalanceReceivable = Arith.sub(Arith.add(beginAmount, totalAmountReceivable), totalWriteOffAmount);

                        // 3.生成一条对账信息
                        CustomerStatementStatisticsEntity statisticsEntity = new CustomerStatementStatisticsEntity();
                        statisticsEntity.setSerialNo("");
                        Optional<FieldAttrEntity> optional = JSONArray.parseArray(paasFormExplainEntity.getExplains(), FieldAttrEntity.class).stream()
                                .filter(item -> Objects.equals(item.getFieldType(), FieldTypeEnum.SERIALNO.getType())).findFirst();
                        if (optional.isPresent()) {
                            // 编号生成
                            FieldAttrEntity fieldAttrEntity = optional.get();
                            SerialNoDTO serialNoDTO = new SerialNoDTO();
                            serialNoDTO.setCorpid(corpid);
                            serialNoDTO.setFormId(formId);
                            serialNoDTO.setSerialNumber(fieldAttrEntity.getSerialNumber());
                            // 生成流水号
                            SerialNoVO serialNoVO = serialNoHelp.getSerialNo(serialNoDTO);
                            if (serialNoVO == null || StringUtils.isEmpty(serialNoVO.getSerialNo())) {
                                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, MessageConstant.NO_SERIAL_NO);
                            }
                            statisticsEntity.setSerialNo(serialNoVO.getSerialNo());
                        }
                        JSONObject statisticsData = new JSONObject();
                        statisticsData.put(CustomerStatementStatisticEnum.CUSTOMER_ID.getAttr(), customerId);
                        statisticsData.put(CustomerStatementStatisticDistributorEnum.STATUS.getAttr(), CustomerStatementStatusEnum.UNCONFIRMED.getCode());
                        statisticsData.put(CustomerStatementStatisticEnum.CREATE_DATE.getAttr(), DateTimeUtil.getInt());
                        statisticsData.put(CustomerStatementStatisticEnum.AMOUNT_RECEIVABLE_TOTAL.getAttr(), totalAmountReceivable);
                        statisticsData.put(CustomerStatementStatisticEnum.AMOUNT_RECEIVED_TOTAL.getAttr(), totalAmountReceived);
                        statisticsData.put(CustomerStatementStatisticEnum.WRITE_OFF_AMOUNT_TOTAL.getAttr(), totalWriteOffAmount);
                        statisticsData.put(CustomerStatementStatisticEnum.BALANCE_RECEIVABLE_TOTAL.getAttr(), totalBalanceReceivable);
                        statisticsData.put(CustomerStatementStatisticEnum.IS_ARCHIVED.getAttr(), ArchivedEnum.UN_ARCHIVED.getCode());
                        statisticsData.put(CustomerStatementStatisticEnum.LINK_CUSTOMER.getAttr(), customerEntity.getData().get(ManagementBaseEnum.NAME.getAttr()));
                        statisticsData.put(CustomerStatementStatisticEnum.CREATOR.getAttr(), customerEntity.getOwnerId());
                        //对账日期
                        JSONObject checkDate = new JSONObject();
                        checkDate.put("startTime",firstTime);
                        checkDate.put("endTime",checkingDate.get(1));
                        statisticsData.put(CustomerStatementStatisticEnum.CHECK_DATE.getAttr(), checkDate);
                        statisticsEntity.setData(statisticsData);
                        statisticsEntity.setCreatorId(customerEntity.getOwnerId());
                        statisticsEntity.setOwnerId(customerEntity.getOwnerId());
                        statisticsEntity.setCorpid(corpid);
                        statisticsEntity.setAppId(paasFormEntity.getAppId());
                        statisticsEntity.setMenuId(paasFormEntity.getMenuId());
                        statisticsEntity.setFormId(formId);
                        statisticsEntity.setFlowStatus(FlowStatusEnum.NOT_NEED_APPROVAL.getType());

                        Long id = customerStatementStatisticsModel.save(statisticsEntity);

                        // 4.数据存入到对账流水快照中
                        addSnapshotData(corpid, list, firstTime, beginAmount, id, distributorMark);
                    } catch (XbbException exception) {
                        LOG.error(exception.getMsg());
                    }
                }
            });
        }
    }

    @Override
    public void dataConsistencyUpdateCustomer(String corpid, Long dataId, String name) {
        try {
            BoolQueryBuilder filter = boolQuery()
                    .filter(termQuery("corpid.keyword", corpid))
                    .filter(termQuery("del", 0))
                    .filter(termQuery(CustomerStatementStatisticEnum.getEsAttr(CustomerStatementStatisticEnum.CUSTOMER_ID), dataId));
            List<String> fieldList = Collections.singletonList(BasicConstant.ID);
            List<CustomerStatementStatisticsEntity> esEntities = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_STATEMENT, filter, CustomerStatementStatisticsEntity.class, fieldList);
            if (CollectionUtils.isEmpty(esEntities)) {
                return;
            }
            Set<Long> ids = esEntities.stream().map(CustomerStatementStatisticsEntity::getId).collect(Collectors.toSet());
            Map<String, Object> param = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            param.put("corpid", corpid);
            param.put("idIn", ids);
            param.put("del", 0);
            List<CustomerStatementStatisticsEntity> entityList = customerStatementStatisticsModel.findEntitys(param);
            if (CollectionUtils.isNotEmpty(entityList)) {
                List<UpdateDataEntity> updateList = new ArrayList<>();
                for (CustomerStatementStatisticsEntity entity : entityList) {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put(CustomerStatementStatisticDistributorEnum.LINK_CUSTOMER.getAttr(), name);
                    UpdateDataEntity updateData = ExplainUtil.getUpdateData(entity.getId(), jsonObject, corpid);
                    updateList.add(updateData);
                }
                customerStatementStatisticsModel.updateBatch(updateList, corpid);
            }

        } catch (Exception e) {
            LOG.error("CustomerStatementStatisticServiceImpl.dataConsistencyUpdateCustomer 出错，corpid=" + corpid + "   customerId=" + dataId + "  customerName=" + name, e);
        }
    }


    /**
     * 校验对帐日期是否有交叉,不允许对账周期有交集
     * @param customerId 对账客户
     * @param corpid
     * @param statementCustomerPojo
     * @return
     * @throws XbbException
     */
    private long checkRepeatStatement(Long customerId, String corpid, StatementCustomerPojo statementCustomerPojo) throws XbbException {
        BoolQueryBuilder queryBuilder = boolQuery();
        queryBuilder.filter(termQuery("corpid.keyword", corpid));
        queryBuilder.filter(termQuery("data." + CustomerStatementStatisticEnum.CUSTOMER_ID.getAttr(), customerId));
        queryBuilder.filter(termQuery("del", 0));
        // 判断对账日期<T1,T2>是否有交集
        // 1. T1<startTime<T2
        // 2. T1<endTime<T2
        // 3. startTime<T1 && endTime>T2

        queryBuilder.should(rangeQuery("data." + CustomerStatementStatisticEnum.CHECK_DATE.getAttr() + ".startTime").gte(statementCustomerPojo.getStartTime()).lte(statementCustomerPojo.getEndTime()));
        queryBuilder.should(rangeQuery("data." + CustomerStatementStatisticEnum.CHECK_DATE.getAttr() + ".endTime").gte(statementCustomerPojo.getStartTime()).lte(statementCustomerPojo.getEndTime()));
        BoolQueryBuilder mustBuilder = boolQuery();
        mustBuilder.must(rangeQuery("data." + CustomerStatementStatisticEnum.CHECK_DATE.getAttr() + ".startTime").lte(statementCustomerPojo.getStartTime()));
        mustBuilder.must(rangeQuery("data." + CustomerStatementStatisticEnum.CHECK_DATE.getAttr() + ".endTime").gte(statementCustomerPojo.getEndTime()));
        queryBuilder.should(mustBuilder);
        queryBuilder.minimumShouldMatch(1);
        queryBuilder.filter(termQuery(ElasticConstant.MY_JOIN_FIELD, IndexTypeEnum.IDX_SAAS_STATEMENT.getType()));
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(queryBuilder);
        SearchRequest searchRequest = new SearchRequest(IndexTypeEnum.IDX_SAAS_STATEMENT.getIndex());
        searchRequest.source(sourceBuilder);
        return xbbElasticsearchRestTemplate.count(searchRequest, CustomerStatementStatisticsEntity.class);
    }
}
